
#ifndef SX_ABSTRACT_RECORDER_H
#define SX_ABSTRACT_RECORDER_H

#include <sxAbstractRecording.h>
#include <sxFixedTimeline.h>

using Sx::Multimedia::FixedTimeline;

namespace Sx {
namespace Multimedia {

template <class FrameType, typename Real>
class AbstractRecorder {
public:
	AbstractRecorder(const std::shared_ptr<AbstractRecording<FrameType, Real> >& recording);
	virtual ~AbstractRecorder();

	/* 
	 * The following set of functions modify the state of this recorder. If the
	 * recorder is recording, frames will be stored into the internal recording
	 * when the processFrame() function is called. Otherwise if this recorder
	 * is stopped or paused, then the processFrame() function will do nothing.
	 */
	void start();
	void pause();
	void resume();
	void stop();
	void setRecording(bool b);

	/*
	 * This function provides the main interface for recording frame information
	 * and storing it into the internal recording. This function should be
	 * called every time that a new frame needs to be stored to the recording
	 *
	 * The implementation of how the domain data is stored in a frame depends
	 * on the implementation of the class that extends this AbstractRecorder
	 * class (see processFrame(FrameType&)).
	 *
	 * TLDR: This function should be called every time a new frame should be 
	 * saved to the internal recording. If the function returns true then a
	 * frame was stored; If it returns false then no frame was stored (it will
	 * also return false when recordingFrames = false).
	 */
	bool processFrame();

	/*
	 * If recording then every time the function processFrame() is called, this
	 * recorder will store a new frame into the internal recording.
	 */
	bool isRecording();
	unsigned int getRecordingLength();

	bool addTimeline(const std::shared_ptr<FixedTimeline>& timeline);
	bool removeTimeline(const std::shared_ptr<FixedTimeline>& timeline);

protected:
	/*
	 * All AbstractRecorder derived class must implement the process that
	 * defines how the domain specific information is encoded into a frame
	 * and stored into the internal recording.
	 */
	virtual bool processFrame(FrameType& frame) = 0;

	/* 
	 * Internal recording. This stores the domain-specific frames of the
	 * recording and their positions or times.
	 */
	std::shared_ptr<AbstractRecording<FrameType, Real> > recording;

	/*
	 * True - When processFrame() is called, a new frame will be stored into
	 * the internal recording.
	 * False - When processFrame() is called, no frames will be added to the
	 * internal recording (basically makes the processFrame() function return
	 * without doing anything).
	 */
	bool recordingFrames;

	/* Timeline collections to update based on the recording process. */
	Util::ArrayList<std::shared_ptr<FixedTimeline> > timelines;
};

}

}

template <class FrameType, typename Real>
Sx::Multimedia::AbstractRecorder<FrameType, Real>::AbstractRecorder(const std::shared_ptr<AbstractRecording<FrameType, Real> >& recording) {
	this->recordingFrames = false;
	this->recording = recording;
}

template <class FrameType, typename Real>
Sx::Multimedia::AbstractRecorder<FrameType, Real>::~AbstractRecorder() {}

template <class FrameType, typename Real>
void Sx::Multimedia::AbstractRecorder<FrameType, Real>::start() {
	this->recordingFrames = true;
}

template <class FrameType, typename Real>
void Sx::Multimedia::AbstractRecorder<FrameType, Real>::pause() {
	this->recordingFrames = false;
}

template <class FrameType, typename Real>
void Sx::Multimedia::AbstractRecorder<FrameType, Real>::resume() {
	this->recordingFrames = true;
}

template <class FrameType, typename Real>
void Sx::Multimedia::AbstractRecorder<FrameType, Real>::stop() {
	this->recordingFrames = false
}

template <class FrameType, typename Real>
void Sx::Multimedia::AbstractRecorder<FrameType, Real>::setRecording(bool b) {
	this->recordingFrames = b;
}

template <class FrameType, typename Real>
bool Sx::Multimedia::AbstractRecorder<FrameType, Real>::processFrame() {
	if ( this->recording == nullptr ) return false;
	if ( this->recording.use_count() == 0 ) return false;

	if ( this->recordingFrames ) {
		bool success = this->processFrame(this->recording->getNextFrame());
		if ( success ) {
			for ( unsigned int i = 0; i < this->timelines.size(); i++ )
				if ( this->timelines[i] != nullptr ) this->timelines[i]->incrementTime();
			return true;
		}
		else return false;
	}
	return false;
}

template <class FrameType, typename Real>
bool Sx::Multimedia::AbstractRecorder<FrameType, Real>::isRecording() {
	return this->recordingFrames;
}

template <class FrameType, typename Real>
unsigned int Sx::Multimedia::AbstractRecorder<FrameType, Real>::getRecordingLength() {
	if ( this->recording == nullptr || this->recording.use_count() == 0 ) return 0;
	return this->recording->getFrameCount();
}

template <class FrameType, typename Real>
bool Sx::Multimedia::AbstractRecorder<FrameType, Real>::addTimeline(const std::shared_ptr<FixedTimeline>& timeline) {
	if ( timeline == nullptr ) {
		std::cerr << "[AbstractRecorder:addTimeline] Error: Cannot add nullptr timeline to recorder." << std::endl;
		return false;
	}

	this->timelines.add(timeline);
	return true;
}

template <class FrameType, typename Real>
bool Sx::Multimedia::AbstractRecorder<FrameType, Real>::removeTimeline(const std::shared_ptr<FixedTimeline>& timeline) {
	return this->timelines.remove(timeline);
}

#endif
